<?php
// {{{ class DocumentObject
/**
 * DocumentObjectクラス
 * jQuery的に出力内容を作成するクラス
 * 
 * TODO: Text、Append等でHTMLを追加する時にDOMとして登録できない
 * 
 * TODO: ID、クラス等タグ以外のセレクタに未対応
 * TODO: 複数オブジェクトの取得に未対応
 */
class DocumentObject {
	public $Tag;
	public $Attributes;
	public $Events;
	public $Styles;

	public $Object;

	public $Children;
	public $Parent;

	public function __construct ($Tag = null) {
		$this->Tag = $Tag;
		$this->ID = array ();
		$this->Class = array ();

		$this->Attributes = array();
		$this->Events = array ();
		$this->Styles = array ();

		$this->Object = null;

		$this->Children = array ();
		$this->Parent = null;
	}

	public function getChildren2Html ($Indent = '', $IndentCnt = 0) {
		$Rt = '';
		$isNL = false;

		// 開始タグの出力
		if ($this->Tag) {
			if ($IndentCnt) $Rt .= "\n".str_repeat ($Indent, $IndentCnt);
			$Rt .= ('<'.$this->Tag);

			// IDの出力
			if (count($this->ID)) {
				$Rt .= ' id="'.implode(' ', $this->ID).'"';
			}
	
			// classの出力
			if (count($this->Class)) {
				$Rt .= ' class="'.implode(' ', $this->Class).'"';
			}

			foreach ($this->Attributes as $Key=>$Val) {
				if (!(is_string($Val) || is_numeric($Val))) continue;
				$Rt .= ' '.$Key.'="'.$Val.'"';
			}
	
			foreach ($this->Events as $Key=>$Val) {
				if (!(is_string($Val) || is_numeric($Val))) continue;
				$Rt .= ' '.$Key.'="'.$Val.'"';
			}



			// 空タグの場合は/>を出力
			if (count($this->Children) == 0 && is_null($this->Object)) {
				$Rt .= (' />');
				return $Rt;
			} else {
				$Rt .= ('>');
			}
		}

		// オブジェクトとcallメソッドがあれば呼び出す
		if ($this->Object && method_exists($this->Object, 'call')) {
			$Rt .= ($this->Object->call($this));
		}

		// Childrenを出力する
		if (count($this->Children)) {
			foreach ((array)$this->Children as $Child) {
				if (is_scalar($Child)) {
					$Rt .= $Child;
				} else {
					if (method_exists($Child, 'getChildren2Html')) {
						$isNL = true;
						$IndentCnt++;
						$Rt .= $Child->getChildren2Html($Indent, $IndentCnt);
						$IndentCnt--;
					}
				}
			}
		}

		// 閉じタグの出力
		if ($this->Tag) {
			if ($isNL) {
				$Rt .= "\n".str_repeat ($Indent, $IndentCnt);
			}
			$Rt .=  ('</'.$this->Tag.'>');
		}
		
		return $Rt;
	}



	/************************* Selectors   *************************/


	/************************* Attributes  *************************/

	/**
	 * 属性を変更する
	 */
	public function attr ($Key, $Value = null) {
		if (is_scalar($Key)) {
			if (is_null($Value)) {
				return isset($this->Attributes[$Key])?$this->Attributes[$Key]:null;
			} else {
				switch (strtolower ($Key)) {
					case ('id') :
					case ('class') :
					case ('style') :
					default:
						$this->Attributes[$Key] = $Value;
				}
			}
		} else {
			foreach ($Key as $Name=>$Value) {
				switch (strtolower ($Name)) {
					case ('id') :
					case ('class') :
					case ('style') :
					default:
						$this->Attributes[$Name] = $Value;
				}
			}
		}
		return $this;
	}
	
	/**
	 * CSSクラスを追加する
	 */
	public function addClass ($Class) {
		$Class = explode (' ', $Class);
		foreach ($Class as $Value) {
			$Value = trim ($Value);
			if ($Value) {
				$this->Class[] = $Value;
			}
		}
		return $this;
	}

	/**
	 * Textの内容を変更する
	 * TODO: 効率が悪い……HTML化を介さずにテキストだけ取るべき。
	 */
	public function text ($Text = null) {
		if (is_null($Text)) {
			return strip_tags($this->getChildren2Html());
		} else {
			$this->Children = array($Text);
		}
		return $this;
	}

	/**
	 * Htmlの内容を変更する
	 */
	public function html ($Html = null) {
		if (is_null($Html)) {
			return $this->getChildren2Html();
		} else {
			// TODO: $HtmlをDocumentObjectに変換できるようにしたい。
			$this->Children = array($Html);
		}
		return $this;
	}

	/************************* Traversing   *************************/

	/**
	 * 配下のオブジェクトを取得する
	 */
	public function children ($Selector) {
		$Selector = trim ($Selector);
		if (!$Selector) return $this;

		$Select = explode (' ', $Selector, 2);

		// とりあえず直下を探す
		// タグとその他を分割
		$Tag = '';
		preg_match ('/^([^#\.]*)/', $Select[0], $matches);

		foreach ($this->Children as $Key=>$Child) {
			if (!$Child->Tag) continue;

			// タグを調査
			if (strcasecmp($Child->Tag, $matches[1]) === 0) {
				if (count($Select) <= 1) {
					return $Child;
				} else {
					return $Child->children($Select[1]);
				}
			}
		}
	}

	/************************* Manipulation *************************/

	/**
	 * 内部に要素を追加する
	 */
	public function append ($Text) {
		$this->Children[] = $Text;
		return $this;
	}
}
//}}}


// {{{ class DocumentObjectGroup
/**
 * DocumentObjectGroupクラス
 * 複数のDocumentObjectクラスを統括し、プログラムとのインターフェイスとなる。
 * 
 * TODO: Text、Append等でHTMLを追加する時にDOMとして登録できない
 * 
 * TODO: ID、クラス等タグ以外のセレクタに未対応
 * TODO: 複数オブジェクトの取得に未対応
 */
class DocumentObjectGroup {
	public $childDOM;
	public $selector;

	public function __construct ($selector = null, $DOM = array()) {
		$this->selector = $selector;
		$this->childDOM = array();
		
		if (is_object($DOM)) {
			if (get_class($DOM) == __CLASS__) {
				$this->childDOM = $DOM->childDOM;
			} else {
				$DOM = array($DOM);
			}
		}

		foreach ((array)$DOM as $child) {
			$this->childDOM[] = $child;
		}
	}

	/**
	 * 擬似Singletonパターン化
	 * 全DOMを保持したDOGを作成、取得する。
	 */
	static function &getAllDOMs ($isNew = false) {
		static $Instance;
		if ($isNew || !isset($Instance)) {
			$Instance = new DocumentObjectGroup (null, new DocumentObject());
		}
		return $Instance;
	}

	/**
	 * DOGに存在しないメソッドはすべてchildDOMに送る
	 */
	public function __call ($name,$arg) {
		$retObj = new DocumentObjectGroup ();
		foreach ($this->childDOM as $DOM) {
			if (method_exists($DOM,$name)) {
				$ret = call_user_func_array(array($DOM,$name), $arg);
				$retObj->DOG_setChild($ret);
			} else {
				throw new YuToriException ('存在しないメソッドを呼び出そうとしました。:'.$name);
			}
		}
		
		return $retObj;
	}

	public function getChildren2Html ($Indent = '', $IndentCnt = 0) {
		$retStr = '';
		foreach ($this->childDOM as $DOM) {
			$retStr .= $DOM->getChildren2Html($Indent, $IndentCnt);
		}
		
		return $retStr;
	}

	/**
	 * childDOMのテキストを取得
	 */
	public function text() {
		$arg = func_get_args();
		if (count($arg)) {
			foreach ($this->childDOM as $DOM) {
				$DOM->text($arg[0]);
			}
			return $this;
		} else {
			$strRet = '';
			foreach ($this->childDOM as $DOM) {
				$strRet .= $DOM->text();
			}
			return $strRet;
		}
	}

	/**
	 * グループに新規DOMをセットする
	 */
	public function DOG_setChild ($DOM) {
		$this->childDOM[] = $DOM;
		return $this;
	}

	/**
	 * すべてのDOMに新規DOMを追加する。
	 * jQueryのappendと動作が異なり、指定できるのはタグのみ。
	 * 戻り値もセットしたオブジェクトのグループとなる。
	 */
	public function DOG_appendChild ($Tag = null) {
		$Rt = new DocumentObjectGroup ();
		foreach ($this->childDOM as $DOM) {
			$newDOM = new DocumentObject ($Tag);
			$DOM->Children[] = $newDOM;
			$newDOM->Parent = $DOM;
			
			$Rt->DOG_setChild($newDOM);

			$AllDOG = DocumentObjectGroup::getAllDOMs();
			$AllDOG->DOG_setChild($newDOM);
		}
		return $Rt;
	}
}
// }}}

?>